{-# LANGUAGE DataKinds, RankNTypes, TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} -- FIXME
module Language.Haskell.Assignment
( assignment
, Syntax
, Grammar
, Term
) where

import Prologue

import           Assigning.Assignment hiding (Assignment, Error, count)
import qualified Assigning.Assignment as Assignment
import qualified Data.Abstract.Name as Name
import           Data.ByteString.Char8 (count)
import qualified Data.List.NonEmpty as NonEmpty
import           Data.Syntax
    (contextualize, emptyTerm, handleError, makeTerm, makeTerm', makeTerm'', makeTerm1, parseError, postContextualize)
import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration
import qualified Data.Syntax.Literal as Literal
import qualified Data.Syntax.Statement as Statement
import qualified Data.Syntax.Type as Type
import qualified Data.Term as Term
import qualified Language.Haskell.Syntax as Syntax
import           TreeSitter.Haskell as Grammar

type Syntax = '[
    Comment.Comment
  , Declaration.Constructor
  , Declaration.Datatype
  , Declaration.Function
  , Literal.Array
  , Literal.Character
  , Literal.Float
  , Literal.Integer
  , Literal.TextElement
  , Literal.Tuple
  , Statement.If
  , Statement.Match
  , Statement.Pattern
  , Syntax.AllConstructors
  , Syntax.AnnotatedTypeVariable
  , Syntax.App
  , Syntax.ArithmeticSequence
  , Syntax.AsPattern
  , Syntax.BindPattern
  , Syntax.CaseGuardPattern
  , Syntax.Class
  , Syntax.ConstructorIdentifier
  , Syntax.ConstructorOperator
  , Syntax.ConstructorPattern
  , Syntax.ConstructorSymbol
  , Syntax.Context
  , Syntax.ContextAlt
  , Syntax.CPPDirective
  , Syntax.DefaultDeclaration
  , Syntax.DefaultSignature
  , Syntax.Deriving
  , Syntax.Do
  , Syntax.Empty
  , Syntax.Error
  , Syntax.EqualityConstraint
  , Syntax.Export
  , Syntax.ExpressionTypeSignature
  , Syntax.Field
  , Syntax.FieldBind
  , Syntax.FieldPattern
  , Syntax.FixityAlt
  , Syntax.FunctionalDependency
  , Syntax.FunctionConstructor
  , Syntax.FunctionGuardPattern
  , Syntax.FunctionType
  , Syntax.GADT
  , Syntax.GADTConstructor
  , Syntax.Generator
  , Syntax.Guard
  , Syntax.HiddenImport
  , Syntax.Identifier
  , Syntax.InfixConstructorIdentifier
  , Syntax.InfixOperatorApp
  , Syntax.InfixVariableIdentifier
  , Syntax.ImplicitParameterIdentifier
  , Syntax.Import
  , Syntax.ImportAlias
  , Syntax.ImportDeclaration
  , Syntax.InfixDataConstructor
  , Syntax.InfixOperatorPattern
  , Syntax.Instance
  , Syntax.IrrefutablePattern
  , Syntax.Kind
  , Syntax.KindFunctionType
  , Syntax.KindListType
  , Syntax.KindParenthesizedConstructor
  , Syntax.KindSignature
  , Syntax.KindTupleType
  , Syntax.LabeledConstruction
  , Syntax.LabeledPattern
  , Syntax.LabeledUpdate
  , Syntax.Lambda
  , Syntax.LambdaCase
  , Syntax.LeftOperatorSection
  , Syntax.Let
  , Syntax.ListComprehension
  , Syntax.ListConstructor
  , Syntax.ListPattern
  , Syntax.Module
  , Syntax.ModuleExport
  , Syntax.ModuleIdentifier
  , Syntax.NamedFieldPun
  , Syntax.NegativeLiteral
  , Syntax.NewType
  , Syntax.PatternGuard
  , Syntax.Pragma
  , Syntax.PrefixNegation
  , Syntax.PrimitiveConstructorIdentifier
  , Syntax.PrimitiveVariableIdentifier
  , Syntax.PromotedTypeOperator
  , Syntax.QualifiedConstructorIdentifier
  , Syntax.QualifiedInfixVariableIdentifier
  , Syntax.QualifiedModuleIdentifier
  , Syntax.QualifiedImportDeclaration
  , Syntax.QualifiedTypeClassIdentifier
  , Syntax.QualifiedTypeConstructorIdentifier
  , Syntax.QualifiedVariableIdentifier
  , Syntax.QuasiQuotation
  , Syntax.QuasiQuotationDeclaration
  , Syntax.QuasiQuotationExpression
  , Syntax.QuasiQuotationExpressionBody
  , Syntax.QuasiQuotationPattern
  , Syntax.QuasiQuotationQuoter
  , Syntax.QuasiQuotationType
  , Syntax.QuotedName
  , Syntax.RecordDataConstructor
  , Syntax.RecordWildCards
  , Syntax.RightOperatorSection
  , Syntax.ScopedTypeVariables
  , Syntax.Splice
  , Syntax.StandaloneDerivingInstance
  , Syntax.Star
  , Syntax.StrictPattern
  , Syntax.StrictType
  , Syntax.StrictTypeVariable
  , Syntax.TupleConstructor
  , Syntax.TupleExpression
  , Syntax.TuplePattern
  , Syntax.Type
  , Syntax.TypeApp
  , Syntax.TypeClass
  , Syntax.TypeClassIdentifier
  , Syntax.TypeClassInstance
  , Syntax.TypeConstructorExport
  , Syntax.TypeConstructorIdentifier
  , Syntax.TypeFamily
  , Syntax.TypeInstance
  , Syntax.TypeOperator
  , Syntax.TypePattern
  , Syntax.TypeSignature
  , Syntax.TypeSynonym
  , Syntax.TypeVariableIdentifier
  , Syntax.UnitConstructor
  , Syntax.VariableIdentifier
  , Syntax.VariableOperator
  , Syntax.VariableSymbol
  , Syntax.ViewPattern
  , Syntax.Wildcard
  , Type.TypeParameters
  , []
  ]

type Term = Term.Term (Sum Syntax) Location
type Assignment = Assignment.Assignment [] Grammar

assignment :: Assignment Term
assignment = handleError $ module' <|> parseError

algebraicDatatypeDeclaration :: Assignment Term
algebraicDatatypeDeclaration = makeTerm
                            <$> symbol AlgebraicDatatypeDeclaration
                            <*> children (Declaration.Datatype
                                        <$> (context' <|> emptyTerm)
                                        <*> (makeTerm <$> location <*> (Syntax.Type <$> typeConstructor <*> typeParameters <*> (kindSignature <|> emptyTerm)))
                                        <*> (constructors <|> pure [])
                                        <*> (term derivingClause <|> emptyTerm))
  where
    constructors = symbol Constructors *> children (manyTerm constructor)

allConstructors :: Assignment Term
allConstructors = makeTerm <$> token AllConstructors <*> pure Syntax.AllConstructors

alternative :: Assignment Term
alternative = makeTerm <$> symbol Alternative <*> children (Statement.Pattern <$> expression <*> expressions)

annotatedTypeVariable :: Assignment Term
annotatedTypeVariable = makeTerm <$> symbol AnnotatedTypeVariable <*> children (Syntax.AnnotatedTypeVariable <$> typeVariableIdentifier <* token Annotation <*> expression)

app :: Assignment Term
app = makeTerm <$> symbol FunctionApplication <*> children (Syntax.App <$> expression <*> (typeApp <|> emptyTerm) <*> expression)

arithmeticSequence :: Assignment Term
arithmeticSequence = symbol ArithmeticSequence *> children (  enumFrom
                                                          <|> enumFromThen
                                                          <|> enumFromTo
                                                          <|> enumFromThenTo)
  where
    enumFrom = makeTerm <$> symbol EnumFrom <*> children (Syntax.ArithmeticSequence <$> expression <*> pure Nothing <*> pure Nothing)
    enumFromThen = makeTerm <$> symbol EnumFromThen <*> children (Syntax.ArithmeticSequence <$> expression <*> fmap Just expression <*> pure Nothing)
    enumFromTo = makeTerm <$> symbol EnumFromTo <*> children (Syntax.ArithmeticSequence <$> expression <*> fmap Just expression <*> pure Nothing)
    enumFromThenTo = makeTerm <$> symbol EnumFromThenTo <*> children (Syntax.ArithmeticSequence <$> expression <*> fmap Just expression <*> fmap Just expression)

asPattern :: Assignment Term
asPattern = makeTerm <$> symbol AsPattern <*> children (Syntax.AsPattern <$> expression <*> expression)

bindPattern :: Assignment Term
bindPattern = makeTerm <$> symbol BindPattern <*> children (Syntax.BindPattern <$> manyTermsTill expression (symbol AnonLAngleMinus) <*> expression)

case' :: Assignment Term
case' = makeTerm <$> symbol CaseExpression <*> children (Statement.Match <$> expression <*> expressions)

caseGuardPattern :: Assignment Term
caseGuardPattern = makeTerm <$> symbol CaseGuardPattern <*> children (Syntax.CaseGuardPattern <$> manyTerm expression)

character :: Assignment Term
character = makeTerm <$> symbol Char <*> (Literal.Character <$> source)

class' :: Assignment Term
class' = makeTerm <$> symbol Class <*> children (Syntax.Class <$> manyTerm expression)

comment :: Assignment Term
comment = makeTerm <$> symbol Comment <*> (Comment.Comment <$> source)

conditionalExpression :: Assignment Term
conditionalExpression = makeTerm <$> symbol ConditionalExpression <*> children (Statement.If <$> expression <*> expression <*> expression)

constructor :: Assignment Term
constructor =  (makeTerm <$> symbol DataConstructor <*> children (Declaration.Constructor <$> manyTerm (context' <|> scopedTypeVariables) <*> typeConstructor <*> typeParameters))
           <|> term (makeTerm <$> symbol RecordDataConstructor <*> children (Syntax.RecordDataConstructor <$> manyTerm (context' <|> scopedTypeVariables)  <*> constructorIdentifier <*> term fields))
           <|> term (makeTerm <$> symbol InfixDataConstructor <*> children (Syntax.InfixDataConstructor <$> manyTerm (context' <|> scopedTypeVariables) <*> expression <*> expression <*> expression))

constructorIdentifier :: Assignment Term
constructorIdentifier = makeTerm <$> symbol ConstructorIdentifier <*> (Syntax.ConstructorIdentifier . Name.name <$> source)

constructorOperator :: Assignment Term
constructorOperator = makeTerm <$> symbol ConstructorOperator <*> children (Syntax.ConstructorOperator <$> expression)

constructorPattern :: Assignment Term
constructorPattern = makeTerm <$> symbol ConstructorPattern <*> children (Syntax.ConstructorPattern <$> expressions)

constructorSymbol :: Assignment Term
constructorSymbol = makeTerm <$> symbol ConstructorSymbol <*> (Syntax.ConstructorSymbol . Name.name <$> source)

context' :: Assignment Term
context' = makeTerm <$> symbol Context <*> children (Syntax.ContextAlt <$> expressions)

contextPattern :: Assignment Term
contextPattern = symbol ContextPattern *> children expressions

cppDirective :: Assignment Term
cppDirective = makeTerm <$> symbol CppDirective <*> (Syntax.CPPDirective <$> source)

defaultDeclaration :: Assignment Term
defaultDeclaration = makeTerm <$> symbol DefaultDeclaration <*> children (Syntax.DefaultDeclaration <$> manyTerm expression)

defaultSignature :: Assignment Term
defaultSignature = makeTerm <$> symbol DefaultSignature <*> children (Syntax.DefaultSignature <$> manyTermsTill expression (symbol Annotation) <* token Annotation <*> manyTerm (context' <|> scopedTypeVariables) <*> expressions)

derivingClause :: Assignment Term
derivingClause = makeTerm <$> symbol Deriving <*> children (Syntax.Deriving <$> manyTerm expression)

do' :: Assignment Term
do' = makeTerm <$> symbol Do <*> children (Syntax.Do <$> manyTerm expression)

equalityConstraint :: Assignment Term
equalityConstraint = makeTerm <$> symbol EqualityConstraint <*> children (Syntax.EqualityConstraint <$> equalityLhs <*> equalityRhs)
  where
    equalityLhs = symbol EqualityLhs *> children expression
    equalityRhs = symbol EqualityRhs *> children expression

export :: Assignment Term
export = makeTerm <$> symbol Export <*> children (Syntax.Export <$> expressions)

expression' :: Assignment Term
expression' = symbol Expression *> children expressions

expressions :: Assignment Term
expressions = makeTerm'' <$> location <*> manyTerm expression

expression :: Assignment Term
expression = term (handleError (choice expressionChoices))

expressionChoices :: [Assignment Term]
expressionChoices = [
                      algebraicDatatypeDeclaration
                    , allConstructors
                    , alternative
                    , annotatedTypeVariable
                    , app
                    , arithmeticSequence
                    , asPattern
                    , bindPattern
                    , case'
                    , caseGuardPattern
                    , character
                    , class'
                    , comment
                    , conditionalExpression
                    , context'
                    , contextPattern
                    , constructorIdentifier
                    , constructorOperator
                    , constructorPattern
                    , constructorSymbol
                    , cppDirective
                    , defaultDeclaration
                    , defaultSignature
                    , derivingClause
                    , do'
                    , equalityConstraint
                    , expression'
                    , expressionTypeSignature
                    , fields
                    , fieldBind
                    , fieldPattern
                    , fixityDeclaration
                    , float
                    , functionalDependency
                    , functionConstructor
                    , functionDeclaration
                    , functionGuardPattern
                    , functionType
                    , gadtConstructor
                    , gadtDeclaration
                    , generator
                    , guard'
                    , implicitParameterIdentifier
                    , importAlias
                    , importDeclaration
                    , infixConstructorIdentifier
                    , infixOperatorApp
                    , infixOperatorPattern
                    , infixVariableIdentifier
                    , instance'
                    , integer
                    , irrefutablePattern
                    , kind
                    , kindListType
                    , kindFunctionType
                    , kindParenthesizedConstructor
                    , kindSignature
                    , kindTupleType
                    , labeledConstruction
                    , labeledPattern
                    , labeledUpdate
                    , lambda
                    , lambdaCase
                    , letExpression
                    , letStatement
                    , listConstructor
                    , listComprehension
                    , listExpression
                    , listPattern
                    , listType
                    , moduleExport
                    , moduleIdentifier
                    , namedFieldPun
                    , negativeLiteral
                    , newType
                    , operator
                    , operatorSection
                    , parenthesizedConstructorOperator
                    , parenthesizedExpression
                    , parenthesizedPattern
                    , parenthesizedTypePattern
                    , pattern'
                    , patternGuard
                    , pragma
                    , prefixNegation
                    , primitiveConstructorIdentifier
                    , primitiveVariableIdentifier
                    , promotedTypeOperator
                    , qualifiedConstructorIdentifier
                    , qualifiedImportDeclaration
                    , qualifiedInfixVariableIdentifier
                    , qualifiedModuleIdentifier
                    , qualifiedTypeClassIdentifier
                    , qualifiedTypeConstructorIdentifier
                    , qualifiedVariableIdentifier
                    , quasiQuotation
                    , quasiQuotationDeclaration
                    , quasiQuotationExpression
                    , quasiQuotationExpressionBody
                    , quasiQuotationPattern
                    , quasiQuotationQuoter
                    , quasiQuotationType
                    , quotedName
                    , recordWildCards
                    , scopedTypeVariables
                    , splice
                    , standaloneDerivingInstance
                    , star
                    , strictPattern
                    , strictType
                    , string
                    , tuple
                    , tuplePattern
                    , tupleType
                    , type'
                    , type''
                    , typeApp
                    , typeClass
                    , typeClassIdentifier
                    , typeClassInstance
                    , typeConstructor
                    , typeFamily
                    , typeInstance
                    , typePattern
                    , typeConstructorExport
                    , typeConstructorIdentifier
                    , typeOperator
                    , typeSignature
                    , typeSynonymDeclaration
                    , typeVariableIdentifier
                    , tuplingConstructor
                    , unitConstructor
                    , variableIdentifier
                    , variableOperator
                    , variableSymbol
                    , viewPattern
                    , where'
                    , wildcard
                    ]

expressionTypeSignature :: Assignment Term
expressionTypeSignature = makeTerm <$> symbol ExpressionTypeSignature <*> children (Syntax.ExpressionTypeSignature <$> manyTermsTill expression (symbol Annotation) <* token Annotation <*> manyTerm (context' <|> scopedTypeVariables) <*> expressions)

fields :: Assignment Term
fields = makeTerm <$> symbol Fields <*> children (manyTerm field)

field :: Assignment Term
field = makeTerm
     <$> symbol Field
     <*> children (Syntax.Field
                 <$> variableIdentifiers
                 <* token Annotation
                 <*> fieldType)
  where
    fieldType = makeTerm <$> location <*> (Syntax.Type <$> term (type' <|> typeVariableIdentifier) <*> typeParameters <*> (kindSignature <|> emptyTerm))

fieldBind :: Assignment Term
fieldBind = makeTerm <$> symbol FieldBind <*> children (Syntax.FieldBind <$> expression <*> expression)

fieldPattern :: Assignment Term
fieldPattern = makeTerm <$> symbol FieldPattern <*> children (Syntax.FieldPattern <$> expression <*> expressions)

fixityDeclaration :: Assignment Term
fixityDeclaration = makeTerm <$> symbol FixityDeclaration <*> children (Syntax.FixityAlt <$> (integer <|> emptyTerm) <*> manyTerm expression)

float :: Assignment Term
float = makeTerm <$> symbol Float <*> (Literal.Float <$> source)

functionalDependency :: Assignment Term
functionalDependency = makeTerm <$> symbol FunctionalDependency <*> children (Syntax.FunctionalDependency <$> expressions)

functionBody :: Assignment Term
functionBody = makeTerm <$> symbol FunctionBody <*> children (manyTerm expression)

functionConstructor :: Assignment Term
functionConstructor = makeTerm <$> token FunctionConstructor <*> pure Syntax.FunctionConstructor

functionDeclaration :: Assignment Term
functionDeclaration = makeTerm
                   <$> symbol FunctionDeclaration
                   <*> children (Declaration.Function []
                               <$> term expression
                               <*> (manyTermsTill expression (symbol FunctionBody) <|> pure [])
                               <*> functionBody)

functionGuardPattern :: Assignment Term
functionGuardPattern = makeTerm <$> symbol FunctionGuardPattern <*> children (Syntax.FunctionGuardPattern <$> manyTerm expression)

functionType :: Assignment Term
functionType = makeTerm <$> symbol FunctionType <*> children (Syntax.FunctionType <$> expression <*> expression)

gadtConstructor :: Assignment Term
gadtConstructor = makeTerm
               <$> symbol GadtConstructor
               <*> children (Syntax.GADTConstructor
                           <$> (context' <|> emptyTerm)
                           <*> expression
                           <* token Annotation
                           <*> expressions)

gadtDeclaration :: Assignment Term
gadtDeclaration = makeTerm
               <$> symbol GadtDeclaration
               <*> children (Syntax.GADT
                           <$> (context' <|> emptyTerm)
                           <*> (makeTerm <$> location <*> (Syntax.Type <$> typeConstructor <*> typeParameters' <*> (kindSignature <|> emptyTerm)))
                           <*> where')
  where
    typeParameters' = makeTerm <$> location <*> manyTermsTill expression (symbol KindSignature <|> symbol Where')

generator :: Assignment Term
generator = makeTerm <$> symbol Generator <*> children (Syntax.Generator <$> expression <*> expression)

guard' :: Assignment Term
guard' = makeTerm <$> symbol Guard <*> children (Syntax.Guard <$> expressions)

hiddenImport :: Assignment Term
hiddenImport = makeTerm <$> symbol Import <*> children (Syntax.HiddenImport <$> expressions)

hiddenImportSpec :: Assignment [Term]
hiddenImportSpec = symbol HiddenImportSpec *> children (manyTerm hiddenImport)

implicitParameterIdentifier :: Assignment Term
implicitParameterIdentifier = makeTerm <$> symbol ImplicitParameterIdentifier <*> (Syntax.ImplicitParameterIdentifier . Name.name <$> source)

import' :: Assignment Term
import' = makeTerm <$> symbol Import <*> children (Syntax.Import <$> expressions)

importAlias :: Assignment Term
importAlias = makeTerm <$> symbol ImportAlias <*> children (Syntax.ImportAlias <$> expression <*> expression)

importDeclaration :: Assignment Term
importDeclaration = makeTerm
                 <$> symbol ImportDeclaration
                 <*> children (Syntax.ImportDeclaration
                             <$> (packageQualifiedImport <|> emptyTerm)
                             <*> expression
                             <*> (importSpec <|> hiddenImportSpec <|> pure []))

importSpec :: Assignment [Term]
importSpec = symbol ImportSpec *> children (manyTerm import')

inClause :: Assignment Term
inClause = symbol InClause *> children expressions

infixConstructorIdentifier :: Assignment Term
infixConstructorIdentifier = makeTerm <$> symbol InfixConstructorIdentifier <*> children (Syntax.InfixConstructorIdentifier . Name.name <$> source)

infixOperatorApp :: Assignment Term
infixOperatorApp = makeTerm <$> symbol InfixOperatorApplication <*> children (Syntax.InfixOperatorApp <$> expression <*> (typeApp <|> emptyTerm) <*> expression <*> (expressions <|> emptyTerm))

infixOperatorPattern :: Assignment Term
infixOperatorPattern = makeTerm <$> symbol InfixOperatorPattern <*> children (Syntax.InfixOperatorPattern <$> expression <*> operator <*> expression)

infixVariableIdentifier :: Assignment Term
infixVariableIdentifier = makeTerm <$> symbol InfixVariableIdentifier <*> children (Syntax.InfixVariableIdentifier . Name.name <$> source)

instance' :: Assignment Term
instance' = makeTerm <$> symbol Instance <*> children (Syntax.Instance <$> expressions)

integer :: Assignment Term
integer = makeTerm <$> symbol Integer <*> (Literal.Integer <$> source)

irrefutablePattern :: Assignment Term
irrefutablePattern = makeTerm <$> symbol IrrefutablePattern <*> children (Syntax.IrrefutablePattern <$> expression)

kind :: Assignment Term
kind = kind'
    <|> kindFunctionType
    <|> kindListType
    <|> kindParenthesizedConstructor
    <|> kindSignature
    <|> kindTupleType
    <|> star

kind' :: Assignment Term
kind' = makeTerm <$> symbol Kind <*> children (Syntax.Kind <$> expression)

kindFunctionType :: Assignment Term
kindFunctionType = makeTerm <$> symbol KindFunctionType <*> children (Syntax.KindFunctionType <$> expression <*> expression)

kindListType :: Assignment Term
kindListType = makeTerm <$> symbol KindListType <*> children (Syntax.KindListType <$> expression)

kindParenthesizedConstructor :: Assignment Term
kindParenthesizedConstructor = makeTerm <$> symbol KindParenthesizedConstructor <*> children (Syntax.KindParenthesizedConstructor <$> expression)

kindSignature :: Assignment Term
kindSignature = makeTerm <$> symbol KindSignature <*> children (Syntax.KindSignature <$ token Annotation <*> expression)

kindTupleType :: Assignment Term
kindTupleType = makeTerm <$> symbol KindTupleType <*> children (Syntax.KindTupleType <$> manyTerm expression)

labeledConstruction :: Assignment Term
labeledConstruction = makeTerm <$> symbol LabeledConstruction <*> children (Syntax.LabeledConstruction <$> expression <*> manyTerm expression)

labeledPattern :: Assignment Term
labeledPattern = makeTerm <$> symbol LabeledPattern <*> children (Syntax.LabeledPattern <$> expressions)

labeledUpdate :: Assignment Term
labeledUpdate = makeTerm <$> symbol LabeledUpdate <*> children (Syntax.LabeledUpdate <$> manyTerm expression)

lambda :: Assignment Term
lambda = makeTerm <$> symbol Lambda <*> children (Syntax.Lambda <$> lambdaHead <*> lambdaBody)
  where
    lambdaHead = symbol LambdaHead *> children expressions
    lambdaBody = symbol LambdaBody *> children expressions

lambdaCase :: Assignment Term
lambdaCase = makeTerm <$> symbol LambdaCase <*> children (Syntax.LambdaCase <$> manyTerm expression)

letExpression :: Assignment Term
letExpression = makeTerm <$> symbol LetExpression <*> children (Syntax.Let <$> manyTermsTill expression (symbol InClause) <*> inClause)

letStatement :: Assignment Term
letStatement = makeTerm <$> symbol LetStatement <*> children (Syntax.Let <$> manyTerm expression <*> emptyTerm)

listComprehension :: Assignment Term
listComprehension = makeTerm <$> symbol ListComprehension <*> children (Syntax.ListComprehension <$> expression <*> manyTerm expression)

listConstructor :: Assignment Term
listConstructor = makeTerm <$> token ListConstructor <*> pure Syntax.ListConstructor

listExpression :: Assignment Term
listExpression = makeTerm <$> symbol ListExpression <*> children (Literal.Array <$> manyTerm listElement)
  where listElement = symbol Expression *> children expression

listPattern :: Assignment Term
listPattern = makeTerm <$> symbol ListPattern <*> children (Syntax.ListPattern <$> expressions)

listType :: Assignment Term
listType = makeTerm <$> symbol ListType <*> children (Literal.Array <$> manyTerm type')

module' :: Assignment Term
module' =  makeTerm
       <$> symbol Module
       <*> children (Syntax.Module
                   <$> manyTerm (comment <|> pragma)
                   <*> term (moduleIdentifier <|> qualifiedModuleIdentifier <|> emptyTerm)
                   <*> moduleExports
                   <*> term (where' <|> expressions <|> emptyTerm))
  where
    moduleExports = symbol ModuleExports *> children (manyTerm export)
                 <|> pure []

moduleExport :: Assignment Term
moduleExport = makeTerm <$> symbol ModuleExport <*> children (Syntax.ModuleExport <$> expressions)

moduleIdentifier :: Assignment Term
moduleIdentifier = makeTerm <$> symbol ModuleIdentifier <*> (Syntax.ModuleIdentifier . Name.name <$> source)

namedFieldPun :: Assignment Term
namedFieldPun = makeTerm <$> symbol NamedFieldPun <*> children (Syntax.NamedFieldPun <$> expression)

negativeLiteral :: Assignment Term
negativeLiteral = makeTerm <$> symbol NegativeLiteral <*> children (Syntax.NegativeLiteral <$> expression)

newConstructor :: Assignment Term
newConstructor = makeTerm <$> symbol NewConstructor <*> children (Declaration.Constructor <$> manyTerm (context' <|> scopedTypeVariables) <*> expression <*> expressions)

newType :: Assignment Term
newType = makeTerm <$> symbol NewtypeDeclaration <*> children (Syntax.NewType <$> manyTerm (context' <|> scopedTypeVariables) <*> typeLeft <*> newConstructor <*> (derivingClause <|> emptyTerm))
  where
    typeLeft = makeTerm <$> location <*> manyTermsTill expression (symbol NewConstructor)

operator :: Assignment Term
operator =  constructorOperator
        <|> typeOperator
        <|> promotedTypeOperator
        <|> variableOperator

operatorSection :: Assignment Term
operatorSection = (makeTerm <$> symbol RightOperatorSection <*> children (Syntax.RightOperatorSection <$> expression <*> expression))
               <|> (makeTerm <$> symbol LeftOperatorSection <*> children (Syntax.LeftOperatorSection <$> expression <*> expression))

packageQualifiedImport :: Assignment Term
packageQualifiedImport = makeTerm <$> symbol PackageQualifiedImport <*> (Literal.TextElement <$> source)

parenthesizedConstructorOperator :: Assignment Term
parenthesizedConstructorOperator = symbol ParenthesizedConstructorOperator *> children expression

parenthesizedExpression :: Assignment Term
parenthesizedExpression = symbol ParenthesizedExpression *> children expressions

parenthesizedPattern :: Assignment Term
parenthesizedPattern = symbol ParenthesizedPattern *> children expressions

parenthesizedTypePattern :: Assignment Term
parenthesizedTypePattern = symbol ParenthesizedTypePattern *> children expressions

pattern' :: Assignment Term
pattern' = symbol Pattern *> children expressions

patternGuard :: Assignment Term
patternGuard = makeTerm <$> symbol PatternGuard <*> children (Syntax.PatternGuard <$> expression <*> (expression <|> emptyTerm))

pragma :: Assignment Term
pragma = makeTerm <$> symbol Pragma <*> (Syntax.Pragma <$> source)

prefixNegation :: Assignment Term
prefixNegation = makeTerm <$> symbol PrefixNegation <*> children (Syntax.PrefixNegation <$> expression)

primitiveConstructorIdentifier :: Assignment Term
primitiveConstructorIdentifier = makeTerm <$> symbol PrimitiveConstructorIdentifier <*> (Syntax.PrimitiveConstructorIdentifier . Name.name <$> source)

primitiveVariableIdentifier :: Assignment Term
primitiveVariableIdentifier = makeTerm <$> symbol PrimitiveVariableIdentifier <*> (Syntax.PrimitiveVariableIdentifier . Name.name <$> source)

promotedTypeOperator :: Assignment Term
promotedTypeOperator = makeTerm <$> symbol PromotedTypeOperator <*> children (Syntax.PromotedTypeOperator <$> expression)

qualifiedConstructorIdentifier :: Assignment Term
qualifiedConstructorIdentifier = makeTerm <$> symbol QualifiedConstructorIdentifier <*> children (Syntax.QualifiedConstructorIdentifier <$> someTerm' expression)

qualifiedImportDeclaration :: Assignment Term
qualifiedImportDeclaration = makeTerm
                          <$> symbol QualifiedImportDeclaration
                          <*> children (Syntax.QualifiedImportDeclaration
                                      <$> (packageQualifiedImport <|> emptyTerm)
                                      <*> expression
                                      <*> (importSpec <|> hiddenImportSpec <|> pure []))

qualifiedInfixVariableIdentifier :: Assignment Term
qualifiedInfixVariableIdentifier = makeTerm <$> symbol QualifiedInfixVariableIdentifier <*> children (Syntax.QualifiedInfixVariableIdentifier <$> someTerm' expression)

qualifiedModuleIdentifier :: Assignment Term
qualifiedModuleIdentifier = makeTerm <$> symbol QualifiedModuleIdentifier <*> children (Syntax.QualifiedModuleIdentifier <$> someTerm' expression)

qualifiedTypeClassIdentifier :: Assignment Term
qualifiedTypeClassIdentifier = makeTerm <$> symbol QualifiedTypeClassIdentifier <*> children (Syntax.QualifiedTypeClassIdentifier <$> someTerm' expression)

qualifiedTypeConstructorIdentifier :: Assignment Term
qualifiedTypeConstructorIdentifier = makeTerm <$> symbol QualifiedTypeConstructorIdentifier <*> children (Syntax.QualifiedTypeConstructorIdentifier <$> someTerm' expression)

qualifiedVariableIdentifier :: Assignment Term
qualifiedVariableIdentifier = makeTerm <$> symbol QualifiedVariableIdentifier <*> children (Syntax.QualifiedVariableIdentifier <$> someTerm' expression)

quasiQuotation :: Assignment Term
quasiQuotation = makeTerm <$> symbol QuasiQuotation <*> children (Syntax.QuasiQuotation <$> (expression <|> emptyTerm) <*> expression)

quasiQuotationDeclaration :: Assignment Term
quasiQuotationDeclaration = makeTerm <$> token QuasiQuotationDeclaration <*> pure Syntax.QuasiQuotationDeclaration

quasiQuotationExpression :: Assignment Term
quasiQuotationExpression = makeTerm <$> token QuasiQuotationExpression <*> pure Syntax.QuasiQuotationExpression

quasiQuotationExpressionBody :: Assignment Term
quasiQuotationExpressionBody = makeTerm <$> symbol QuasiQuotationExpressionBody <*> (Syntax.QuasiQuotationExpressionBody . Name.name <$> source)

quasiQuotationPattern :: Assignment Term
quasiQuotationPattern = makeTerm <$> token QuasiQuotationPattern <*> pure Syntax.QuasiQuotationPattern

quasiQuotationQuoter :: Assignment Term
quasiQuotationQuoter = makeTerm <$> symbol QuasiQuotationQuoter <*> (Syntax.QuasiQuotationQuoter . Name.name <$> source)

quasiQuotationType :: Assignment Term
quasiQuotationType = makeTerm <$> token QuasiQuotationType <*> pure Syntax.QuasiQuotationType

quotedName :: Assignment Term
quotedName = makeTerm <$> symbol QuotedName <*> children (Syntax.QuotedName <$> expression)

recordWildCards :: Assignment Term
recordWildCards = makeTerm <$> symbol RecordWildCards <*> (Syntax.RecordWildCards <$ source)

scopedTypeVariables :: Assignment Term
scopedTypeVariables = makeTerm <$> symbol ScopedTypeVariables <*> children (Syntax.ScopedTypeVariables <$> expressions <* token Dot)

splice :: Assignment Term
splice = makeTerm <$> symbol Splice <*> children (Syntax.Splice <$> expression)

standaloneDerivingInstance :: Assignment Term
standaloneDerivingInstance = makeTerm <$> symbol StandaloneDerivingDeclaration <*> children (Syntax.StandaloneDerivingInstance <$> manyTerm (context' <|> scopedTypeVariables) <*> expression <*> instance')
  where
    instance' = symbol Instance *> children expressions

star :: Assignment Term
star = makeTerm <$> token Star <*> pure Syntax.Star

strictPattern :: Assignment Term
strictPattern = makeTerm <$> symbol StrictPattern <*> children (Syntax.StrictPattern <$> expression)

strictType :: Assignment Term
strictType = makeTerm'
          <$> symbol StrictType
          <*> children (  (inject <$> (Syntax.StrictType <$> typeConstructor <*> typeParameters))
                      <|> (inject <$> (Syntax.StrictTypeVariable <$> expression)))

string :: Assignment Term
string = makeTerm <$> symbol String <*> (Literal.TextElement <$> source)

tuple :: Assignment Term
tuple = makeTerm <$> symbol TupleExpression <*> children (Syntax.TupleExpression <$> manyTerm expression)

tuplePattern :: Assignment Term
tuplePattern = makeTerm <$> symbol TuplePattern <*> children (Syntax.TuplePattern <$> manyTerm expression)

tupleType :: Assignment Term
tupleType = makeTerm <$> symbol TupleType <*> children (Literal.Tuple <$> manyTerm expression)

tuplingConstructor :: Assignment Term
tuplingConstructor = makeTerm <$> symbol TuplingConstructor <*> (tupleWithArity <$> rawSource)
        -- a tuple (,) has arity two, but only one comma, so apply the successor to the count of commas for the correct arity.
  where tupleWithArity = Syntax.TupleConstructor . succ . count ','

type' :: Assignment Term
type' =  class'
     <|> fields
     <|> functionType
     <|> parenthesizedTypePattern
     <|> strictType
     <|> type''
     <|> typeConstructor
     <|> typePattern

type'' :: Assignment Term
type'' = makeTerm
      <$> symbol Type
      <*> children (Syntax.Type <$> expression <*> typeParameters <*> (kindSignature <|> emptyTerm))

typeApp :: Assignment Term
typeApp = makeTerm <$> symbol TypeApplication <*> children (Syntax.TypeApp <$> expression)

typeClass :: Assignment Term
typeClass = makeTerm <$> symbol TypeClassDeclaration <*> children (Syntax.TypeClass
                                                                 <$> (context' <|> emptyTerm)
                                                                 <*> expression
                                                                 <*> manyTermsTill expression (symbol Where)
                                                                 <*> where')

typeClassIdentifier :: Assignment Term
typeClassIdentifier = makeTerm <$> symbol TypeClassIdentifier <*> (Syntax.TypeClassIdentifier . Name.name <$> source)

typeClassInstance :: Assignment Term
typeClassInstance = makeTerm <$> symbol TypeClassInstanceDeclaration <*> children (Syntax.TypeClassInstance
                                                                      <$> manyTerm (context' <|> scopedTypeVariables)
                                                                      <*> expression
                                                                      <*> expression
                                                                      <*> (where' <|> emptyTerm))

typeConstructor :: Assignment Term
typeConstructor =  constructorIdentifier
               <|> functionConstructor
               <|> listConstructor
               <|> listType
               <|> qualifiedModuleIdentifier
               <|> qualifiedTypeClassIdentifier
               <|> qualifiedTypeConstructorIdentifier
               <|> quotedName
               <|> tupleType
               <|> tuplingConstructor
               <|> typeClassIdentifier
               <|> typeConstructorIdentifier
               <|> unitConstructor

typeConstructorExport :: Assignment Term
typeConstructorExport = makeTerm <$> symbol TypeConstructorExport <*> children (Syntax.TypeConstructorExport <$> expression)

typeConstructorIdentifier :: Assignment Term
typeConstructorIdentifier = makeTerm <$> symbol TypeConstructorIdentifier <*> (Syntax.TypeConstructorIdentifier . Name.name <$> source)

typeFamily :: Assignment Term
typeFamily = makeTerm <$> symbol TypeFamilyDeclaration <*> children (Syntax.TypeFamily <$> expression <*> manyTermsTill expression typeFamilySeperator <*> (typeSignature <|> kindSignature <|> emptyTerm) <*> (where' <|> emptyTerm))
  where
    typeFamilySeperator =  symbol TypeSignature
                       <|> symbol KindSignature
                       <|> symbol Where

typeInstance :: Assignment Term
typeInstance = makeTerm <$> symbol TypeInstanceDeclaration <*> children (Syntax.TypeInstance <$> typeInstanceType <*> typeInstanceBody)
  where
    typeInstanceType = makeTerm <$> location <*> manyTermsTill expression (symbol TypeInstanceBody)
    typeInstanceBody = symbol TypeInstanceBody *> children expressions

typeOperator :: Assignment Term
typeOperator = makeTerm <$> symbol TypeOperator <*> (Syntax.TypeOperator . Name.name <$> source)

typeSignature :: Assignment Term
typeSignature = makeTerm <$> symbol TypeSignature <*> children (Syntax.TypeSignature <$> manyTermsTill expression (symbol Annotation) <* token Annotation <*> manyTerm (context' <|> scopedTypeVariables) <*> expressions)

typeParameters :: Assignment Term
typeParameters = makeTerm <$> location <*> (Type.TypeParameters <$> (manyTermsTill expression (symbol Annotation) <|> manyTerm expression))

typePattern :: Assignment Term
typePattern = makeTerm <$> symbol TypePattern <*> children (Syntax.TypePattern <$> expressions)

typeSynonymDeclaration :: Assignment Term
typeSynonymDeclaration = makeTerm
                      <$> symbol TypeSynonymDeclaration
                      <*> children (typeSynonym <$> typeLeft <*> typeRight)
  where
    typeLeft = makeTerm <$> location <*> manyTill expression typeRightSeperator
    typeRight = (symbol TypeSynonymBody *> children ((,) <$> manyTerm (context' <|> scopedTypeVariables) <*> expression))
             <|> ((,) [] <$> typeSignature)
             <|> ((,) [] <$> kindSignature)
    typeRightSeperator =  symbol TypeSynonymBody
                      <|> symbol TypeSignature
                      <|> symbol KindSignature
    typeSynonym typeLeft (contexts, typeRight) = Syntax.TypeSynonym typeLeft contexts typeRight

typeVariableIdentifier :: Assignment Term
typeVariableIdentifier = makeTerm <$> symbol TypeVariableIdentifier <*> (Syntax.TypeVariableIdentifier . Name.name <$> source)

unitConstructor :: Assignment Term
unitConstructor = makeTerm <$> token UnitConstructor <*> pure Syntax.UnitConstructor

variableIdentifier :: Assignment Term
variableIdentifier = makeTerm <$> symbol VariableIdentifier <*> (Syntax.VariableIdentifier . Name.name <$> source)

variableOperator :: Assignment Term
variableOperator = makeTerm <$> symbol VariableOperator <*> children (Syntax.VariableOperator <$> expression)

variableSymbol :: Assignment Term
variableSymbol = makeTerm <$> (symbol VariableSymbol <|> symbol VariableSymbol') <*> (Syntax.VariableSymbol . Name.name <$> source)

variableIdentifiers :: Assignment Term
variableIdentifiers = makeTerm <$> location <*> many variableIdentifier

viewPattern :: Assignment Term
viewPattern = makeTerm <$> symbol ViewPattern <*> children (Syntax.ViewPattern <$> expression <*> expression)

where' :: Assignment Term
where' = makeTerm <$> (symbol Where <|> symbol Where') <*> children (manyTerm expression)

wildcard :: Assignment Term
wildcard = makeTerm <$> token Wildcard <*> pure Syntax.Wildcard

-- | Helpers

commentedTerm :: Assignment Term -> Assignment Term
commentedTerm term = contextualize (comment <|> pragma) term <|> makeTerm1 <$> (Syntax.Context <$> some1 (comment <|> pragma) <*> emptyTerm)

manyTerm :: Assignment Term -> Assignment [Term]
manyTerm = many . commentedTerm

manyTermsTill :: Assignment Term -> Assignment b -> Assignment [Term]
manyTermsTill step = manyTill (step <|> comment)

someTerm' :: Assignment Term -> Assignment (NonEmpty Term)
someTerm' = NonEmpty.some1 . commentedTerm

term :: Assignment Term -> Assignment Term
term term = contextualize (comment <|> pragma) (postContextualize (comment <|> pragma) term)
